package dawnland03.model.task;

import dawnland03.model.action.parameter.ActionOutputParam;
import dawnland03.model.action.parameter.OutputParameters;
import dawnland03.model.entity.Entity;
import dawnland03.model.item.ItemType;
import dawnland03.model.item.Item;
import dawnland03.model.utility.Utility;

import java.util.concurrent.CountDownLatch;
import java.util.*;

/**
 * User: Petru Obreja (obrejap@yahoo.com)
 * Date: Jan 21, 2010
 * Time: 10:58:29 PM
 * <p/>
 * GroupTask (GT) with 2 base-subtasks (ST1, ST2)
 * GT.init, ST1.init, ST2.init
 * GT.execute
 * GT.prepare
 * GT.executeNextSubTask
 * ST1.execute
 * ST1.prepare
 * action.act
 * action.end
 * ST1.end
 * ST1.executeNextTaskFromParent
 * ST2.execute
 * ..
 * ST2.executeNextTaskFromParent
 * GT.end
 */
public abstract class Task {
    protected final Integer id;
    protected final String name; //the name of the task
    protected final Entity entity; //the entity owner of this task
    protected Map<TaskStatus, TaskCondition> afterStatusCondition; //this task conditions will be called after the 'key' task status
    protected OutputParameters outputParameters; //the total output of subtasks; each subtask adds its oPs to its parentTask, when ends

    protected GroupTask parentTask; //parent group task
    private TaskStatus taskStatus;
    private final CountDownLatch doneSignal;

    protected Task(String name, Entity entity) {
        id = Utility.getNextUuid();
        this.name = name;
        this.entity = entity;
        taskStatus = TaskStatus.CREATED;
        afterStatusCondition = new HashMap<TaskStatus, TaskCondition>();
        outputParameters = new OutputParameters();
        doneSignal = new CountDownLatch(1);
    }

    public void addTaskConditionAfterTaskStatus(TaskStatus taskStatus, TaskCondition taskCondition) {
        afterStatusCondition.put(taskStatus, taskCondition);
    }

    protected final void setParentTask(GroupTask parentTask) {
        this.parentTask = parentTask;
    }

    public GroupTask getParentTask() {
        return parentTask;
    }

    public Task getTopmostParentTask() {
        if (parentTask == null) {
            return this;
        }
        return parentTask.getTopmostParentTask();
    }

    public TaskStatus getTaskStatus() {
        return taskStatus;
    }

    public void waitForTaskToEnd() {
        try {
            doneSignal.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    protected final void setTaskStatus(TaskStatus taskStatus) {
        switch (taskStatus) {
            case CREATED:
                throw new IllegalStateException("Task status can't be set to " + taskStatus);
            case INITIALISED:
                if (this.taskStatus != TaskStatus.CREATED) {
                    throw new IllegalStateException("Current taskStatus: " + this.taskStatus + ". Task status can't be set to " + taskStatus);
                }
                break;
            case PREPARED:
                if (this.taskStatus != TaskStatus.INITIALISED) {
                    throw new IllegalStateException("Current taskStatus: " + this.taskStatus + ". Task status can't be set to " + taskStatus);
                }
                break;
            case RUNNING:
                if (this.taskStatus != TaskStatus.PREPARED) {
                    throw new IllegalStateException("Current taskStatus: " + this.taskStatus + ". Task status can't be set to " + taskStatus);
                }
                break;
            case FINISHED:
                if (this.taskStatus != TaskStatus.RUNNING) {
                    throw new IllegalStateException("Current taskStatus: " + this.taskStatus + ". Task status can't be set to " + taskStatus);
                }
                break;
        }
        this.taskStatus = taskStatus;
    }

    protected void doNextStep() {
        setTaskStatus(taskStatus.getNextStatus());
        if (taskStatus == TaskStatus.FINISHED) {
            if (parentTask != null) {
                //add all output parameters to parentTask
                parentTask.outputParameters.addOutputParamValues(outputParameters);
            }
        }
        callTaskCondition();
    }

    private void callTaskCondition() {
        TaskCondition taskCondition = afterStatusCondition.get(taskStatus);
        if (taskCondition != null) {
            taskCondition.isTaskCompleted(this);
        }
    }

    public void executeTask() {
        while (getTaskStatus() != TaskStatus.getLastStatus()) {
            doNextStep();
        }
        doneSignal.countDown();
    }

    protected abstract boolean isGroupTask();

    protected abstract Task duplicate();

    public OutputParameters getOutputParameters() {
        return outputParameters;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Task task = (Task) o;

        if (!id.equals(task.id)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }
}
